package com.lwlk.util.tuple;

import java.io.Serializable;
import java.util.*;

import java.util.stream.StreamSupport;

public final class Tuple10<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10> 
    implements Iterable<Object>,
               Serializable,
               Comparable<Tuple10> {
    
    private static final int size = 10;
    
    private final T1 value1;
	private final T2 value2;
	private final T3 value3;
	private final T4 value4;
	private final T5 value5;
	private final T6 value6;
	private final T7 value7;
	private final T8 value8;
	private final T9 value9;
	private final T10 value10;
    
    public Tuple10(final T1 arg1, final T2 arg2, final T3 arg3, final T4 arg4, final T5 arg5, final T6 arg6, final T7 arg7, final T8 arg8, final T9 arg9, final T10 arg10) {
        this.value1 = arg1;
		this.value2 = arg2;
		this.value3 = arg3;
		this.value4 = arg4;
		this.value5 = arg5;
		this.value6 = arg6;
		this.value7 = arg7;
		this.value8 = arg8;
		this.value9 = arg9;
		this.value10 = arg10;
    }
    
    public static <T1, T2, T3, T4, T5, T6, T7, T8, T9, T10> Tuple10<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10> with(final T1 arg1, final T2 arg2, final T3 arg3, final T4 arg4, final T5 arg5, final T6 arg6, final T7 arg7, final T8 arg8, final T9 arg9, final T10 arg10) {
        return new Tuple10<>(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10);
    }

    public static <X> Tuple10<X, X, X, X, X, X, X, X, X, X> fromArray(final X[] array) {
        if (array == null) {
            throw new IllegalArgumentException("Array cannot be null");
        }
        if (array.length != 10) {
            throw new IllegalArgumentException("Array must have exactly 10 elements in order to create this Tuple. Size is " + array.length);
        }      
        return new Tuple10<>(array[0], array[1], array[2], array[3], array[4], array[5], array[6], array[7], array[8], array[9]);
    }
    
    public static <X> Tuple10<X, X, X, X, X, X, X, X, X, X> fromCollection(final Collection<X> coll) {
        return fromIterable(coll);
    }
    
    public static <X> Tuple10<X, X, X, X, X, X, X, X, X, X> fromIterable(final Iterable<X> iterable) {
        return fromIterable(iterable, 0);
    }
    
    @SuppressWarnings("unchecked")
    public static <X> Tuple10<X, X, X, X, X, X, X, X, X, X> fromIterable(final Iterable<X> iterable, final int index) {
        if (iterable == null) {
            throw new IllegalArgumentException("Iterable cannot be null");
        }
        return fromArray((X[]) StreamSupport.stream(iterable.spliterator(), false).skip(index).toArray());
    }
    
    public <X> Tuple10<X, T2, T3, T4, T5, T6, T7, T8, T9, T10> setAt1(final X arg) { return new Tuple10<>(arg,value2,value3,value4,value5,value6,value7,value8,value9,value10); }
	public <X> Tuple10<T1, X, T3, T4, T5, T6, T7, T8, T9, T10> setAt2(final X arg) { return new Tuple10<>(value1,arg,value3,value4,value5,value6,value7,value8,value9,value10); }
	public <X> Tuple10<T1, T2, X, T4, T5, T6, T7, T8, T9, T10> setAt3(final X arg) { return new Tuple10<>(value1,value2,arg,value4,value5,value6,value7,value8,value9,value10); }
	public <X> Tuple10<T1, T2, T3, X, T5, T6, T7, T8, T9, T10> setAt4(final X arg) { return new Tuple10<>(value1,value2,value3,arg,value5,value6,value7,value8,value9,value10); }
	public <X> Tuple10<T1, T2, T3, T4, X, T6, T7, T8, T9, T10> setAt5(final X arg) { return new Tuple10<>(value1,value2,value3,value4,arg,value6,value7,value8,value9,value10); }
	public <X> Tuple10<T1, T2, T3, T4, T5, X, T7, T8, T9, T10> setAt6(final X arg) { return new Tuple10<>(value1,value2,value3,value4,value5,arg,value7,value8,value9,value10); }
	public <X> Tuple10<T1, T2, T3, T4, T5, T6, X, T8, T9, T10> setAt7(final X arg) { return new Tuple10<>(value1,value2,value3,value4,value5,value6,arg,value8,value9,value10); }
	public <X> Tuple10<T1, T2, T3, T4, T5, T6, T7, X, T9, T10> setAt8(final X arg) { return new Tuple10<>(value1,value2,value3,value4,value5,value6,value7,arg,value9,value10); }
	public <X> Tuple10<T1, T2, T3, T4, T5, T6, T7, T8, X, T10> setAt9(final X arg) { return new Tuple10<>(value1,value2,value3,value4,value5,value6,value7,value8,arg,value10); }
	public <X> Tuple10<T1, T2, T3, T4, T5, T6, T7, T8, T9, X> setAt10(final X arg) { return new Tuple10<>(value1,value2,value3,value4,value5,value6,value7,value8,value9,arg); }
    
    public Tuple9<T2, T3, T4, T5, T6, T7, T8, T9, T10> removeAt1() { return new Tuple9<>(value2, value3, value4, value5, value6, value7, value8, value9, value10); }
	public Tuple9<T1, T3, T4, T5, T6, T7, T8, T9, T10> removeAt2() { return new Tuple9<>(value1, value3, value4, value5, value6, value7, value8, value9, value10); }
	public Tuple9<T1, T2, T4, T5, T6, T7, T8, T9, T10> removeAt3() { return new Tuple9<>(value1, value2, value4, value5, value6, value7, value8, value9, value10); }
	public Tuple9<T1, T2, T3, T5, T6, T7, T8, T9, T10> removeAt4() { return new Tuple9<>(value1, value2, value3, value5, value6, value7, value8, value9, value10); }
	public Tuple9<T1, T2, T3, T4, T6, T7, T8, T9, T10> removeAt5() { return new Tuple9<>(value1, value2, value3, value4, value6, value7, value8, value9, value10); }
	public Tuple9<T1, T2, T3, T4, T5, T7, T8, T9, T10> removeAt6() { return new Tuple9<>(value1, value2, value3, value4, value5, value7, value8, value9, value10); }
	public Tuple9<T1, T2, T3, T4, T5, T6, T8, T9, T10> removeAt7() { return new Tuple9<>(value1, value2, value3, value4, value5, value6, value8, value9, value10); }
	public Tuple9<T1, T2, T3, T4, T5, T6, T7, T9, T10> removeAt8() { return new Tuple9<>(value1, value2, value3, value4, value5, value6, value7, value9, value10); }
	public Tuple9<T1, T2, T3, T4, T5, T6, T7, T8, T10> removeAt9() { return new Tuple9<>(value1, value2, value3, value4, value5, value6, value7, value8, value10); }
	public Tuple9<T1, T2, T3, T4, T5, T6, T7, T8, T9> removeAt10() { return new Tuple9<>(value1, value2, value3, value4, value5, value6, value7, value8, value9); }

    public Object getValue(final int pos) { 
        if(pos > getSize() || pos <= 0) {
            throw new IllegalArgumentException(
                    "Cannot retrieve position " + pos + " in " + this.getClass().getSimpleName() + 
                    ". Positions for this class start with 1 and end with " + getSize());
        }      
        switch(pos) {
            case 1: return value1;
			case 2: return value2;
			case 3: return value3;
			case 4: return value4;
			case 5: return value5;
			case 6: return value6;
			case 7: return value7;
			case 8: return value8;
			case 9: return value9;
			case 10: return value10;
        }
        return null; // just make java happy!
    }
    
    public List<Object> toList() {
        return Collections.unmodifiableList(Arrays.asList(value1, value2, value3, value4, value5, value6, value7, value8, value9, value10));
    }
    
    public Object[] toArray() {
        return new Object[] { value1, value2, value3, value4, value5, value6, value7, value8, value9, value10 };
    }

    @Override
    public Iterator<Object> iterator() { return toList().iterator(); }

    @Override
    public int hashCode() {
        return Objects.hash(value1, value2, value3, value4, value5, value6, value7, value8, value9, value10);
    }
    
    @Override
    public boolean equals(final Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final Tuple10 tmp = (Tuple10) obj;
        return this.compareTo(tmp) == 0;
    }    
    
    @Override
    public int compareTo(final Tuple10 t) {
        for(int i = 1; i <= getSize(); ++i) {
            final int comparison = ((Comparable) getValue(i)).compareTo(t.getValue(i));
            if(comparison != 0) return comparison;
        }
        return 0;
    }
    
    public int getSize() { return size; }
    
    public T1 getValue1() { return value1; }
    public T2 getValue2() { return value2; }
    public T3 getValue3() { return value3; }
    public T4 getValue4() { return value4; }
    public T5 getValue5() { return value5; }
    public T6 getValue6() { return value6; }
    public T7 getValue7() { return value7; }
    public T8 getValue8() { return value8; }
    public T9 getValue9() { return value9; }
    public T10 getValue10() { return value10; }
}
